﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;
using RSSFeedReader.Commands;
using RSSFeedReader.Controls;
using RSSFeedReader.Data.Models;
using RSSFeedReader.Data.Utilities;
using RSSFeedReader.Resources;
using Forms = System.Windows.Forms;

namespace RSSFeedReader.ViewModels
{
    /// <summary>
    /// View Model only exposes a subset of the underlying
    /// model properties.
    /// </summary>
    public class MainViewModel : ViewModelBase
    {
        #region Members

        DelegateCommand _exitCommand;
        DelegateCommand _newCommand;
        DelegateCommand _openInBrowserCommand;
        DelegateCommand _syncChannelCommand;
        DelegateCommand _synchAllChannelsCommand;
        DelegateCommand _importFeedsCommand;
        DelegateCommand _exportCommand;
        DelegateCommand _deleteChannelCommand;
        DelegateCommand _deleteAllChannelsCommand;
        DelegateCommand _copyLinkCommand;
        DelegateCommand _manageFeedsCommand;

        ChannelViewModel _currentChannel;
        readonly ObservableCollection<ChannelViewModel> _channels;
        static ObservableCollection<ChannelViewModel> _channelCollection;

        DispatcherTimer _timer;
        DateTime _lastUpdated;

        #endregion

        #region Constructor

        /// <summary>
        /// Initialize a new instance of the MainViewModel class, with a
        /// reference to a <see cref="RSSFeedReader.Views.MainView"/> instance.
        /// </summary>
        public MainViewModel()
        {
            _channels = new ObservableCollection<ChannelViewModel>();
            Initialize();
        }

        /// <summary>
        /// Initialize data.
        /// </summary>
        private void Initialize()
        {
            // Hook up events
            ChannelDataSource.Instance.ChannelAdded += OnChannelAdded;
            ChannelDataSource.Instance.ChannelSynchronised += OnChannelSynched;
            ChannelDataSource.Instance.ChannelRemoved += OnChannelRemoved;
            ChannelDataSource.Instance.ChannelFolderAdded += OnChannelFolderAdded;
            ChannelDataSource.Instance.ChannelMovedToFolder += OnChannelMovedToFolder;
            ChannelDataSource.Instance.ChannelMoved += OnChannelMoved;
            ((RSSFeedReaderApplication)Application.Current).DragDropCompleted += OnChannelDragDropCompleted;

            // Listen to unhandled ApplicationError
            Application.Current.DispatcherUnhandledException += OnDispatcherUnhandledException;

            // Load the feeds
            LoadChannels();

            // Set the static channel collection
            _channelCollection = _channels;

            // Init refresh timer
            _timer = new DispatcherTimer();
            _timer.Interval = TimeSpan.FromMinutes(ChannelDataSource.Instance.RefreshPeriod);
            _timer.Tick += OnRefreshTimer;
            _timer.Start();
        }

        /// <summary>
        /// Load the cached channels from the ChannelDataSource, then update.
        /// </summary>
        private void LoadChannels()
        {
            ChannelViewModel channelViewModel = null;

            foreach (Channel channel in ChannelDataSource.Instance.Channels)
            {
                // Create the new ChannelViewModel and add to the _channels collection.
                channelViewModel = ChannelViewModel.CreateNew(channel);
                InitializeChannelViewModel(channelViewModel);

                // Add to the _channelFolders collection.
                _channels.Add(channelViewModel);
            }

            // Put thread to sleep to allow network connection availability to run
            System.Threading.Thread.Sleep(1000);

            // Update channels
            //SetIsLoadingAll();
            //ChannelDataSource.Instance.SyncAllChannels();
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets an ObservableCollection of type <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/>.
        /// </summary>
        public ObservableCollection<ChannelViewModel> Channels
        {
            get { return _channels; }
        }

        /// <summary>
        /// Gets the currently selected <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/> instance.
        /// </summary>
        public ChannelViewModel CurrentChannel
        {
            get { return _currentChannel; }
            set
            {
                if (_currentChannel == value)
                    return;

                _currentChannel = value;

                // Could be set to null from Synching, if left as null
                // would see the error text in the PostView
                if (_currentChannel != null)
                {
                    if (_currentChannel.SelectedPost == null && _currentChannel.ChannelType == ChannelType.Feed)
                    {
                        _currentChannel.Posts[0].IsSelected = true;
                    }
                }

                OnPropertyChanged("CurrentChannel");
            }
        }

        /// <summary>
        /// Gets the last time the feeds were updated.
        /// </summary>
        public DateTime LastUpdated
        {
            get { return _lastUpdated; }
        }

        /// <summary>
        /// Gets the timer that handles refreshing of feeds.
        /// </summary>
        public DispatcherTimer Timer
        {
            get { return _timer; }
        }

        #endregion

        #region Commands

        #region ExitCommand
        /// <summary>
        /// Exit command to close the application.
        /// </summary>
        public ICommand ExitCommand
        {
            get
            {
                if (_exitCommand == null)
                {
                    _exitCommand = new DelegateCommand(DoExit);
                }
                return _exitCommand;
            }
        }

        /// <summary>
        /// Execute the ExitCommand.
        /// </summary>
        void DoExit()
        {
            OnRequestClose();
        }
        #endregion

        #region AddNewFeedCommand
        /// <summary>
        /// AddNewFeed command to add a new RSS feed.
        /// </summary>
        public ICommand AddNewFeedCommand
        {
            get
            {
                if (_newCommand == null)
                {
                    _newCommand = new DelegateCommand(DoAddNewFeedCommand, CanDoAddNewFeedCommand);
                }
                return _newCommand;
            }
        }

        /// <summary>
        /// Determines of the AddNewFeedCommand can be executed.
        /// </summary>
        /// <returns>True if the AddNewFeedCommand can be executed.</returns>
        bool CanDoAddNewFeedCommand()
        {
            return ChannelDataSource.Instance.IsNetworkAvailable;
        }

        /// <summary>
        /// Executes the AddNewFeedCommand.
        /// </summary>
        void DoAddNewFeedCommand()
        {
            DisplayWindow.ShowAddNewFeedView();
        }
        #endregion

        #region OpenInBrowserCommand
        /// <summary>
        /// Open in browser command to open the post in the default browser.
        /// </summary>
        public ICommand OpenInBrowserCommand
        {
            get
            {
                if (_openInBrowserCommand == null)
                    _openInBrowserCommand = new DelegateCommand(DoOpenInBrowserCommand);

                return _openInBrowserCommand;
            }
        }

        /// <summary>
        /// Executes the OpenInBrowserCommand.
        /// </summary>
        void DoOpenInBrowserCommand()
        {
            System.Diagnostics.Process.Start(CurrentChannel.SelectedPost.Link);
        }
        #endregion

        #region SyncChannelCommand
        /// <summary>
        /// Sync the selected feed.
        /// </summary>
        public ICommand SyncChannelCommand
        {
            get
            {
                if (_syncChannelCommand == null)
                    _syncChannelCommand = new DelegateCommand(DoSyncChannelCommand, CanDoSyncChannelCommand);

                return _syncChannelCommand;
            }
        }

        /// <summary>
        /// Determines if the SyncChannelCommand can be executed.
        /// </summary>
        /// <returns>
        /// True if the SyncChannelCommand can be executed.
        /// </returns>
        bool CanDoSyncChannelCommand()
        {
            if (CurrentChannel != null)
            {
                return CurrentChannel.ChannelType == ChannelType.Feed &&
                    ChannelDataSource.Instance.IsNetworkAvailable;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Executes the SyncChannelComand.
        /// </summary>
        void DoSyncChannelCommand()
        {
            CurrentChannel.IsLoading = true;
            ChannelDataSource.Instance.SyncChannel(CurrentChannel.ChannelID);
        }
        #endregion

        #region SyncAllChannelsCommand
        /// <summary>
        /// Syncs all the feeds.
        /// </summary>
        public ICommand SyncAllChannelsCommand
        {
            get
            {
                if (_synchAllChannelsCommand == null)
                    _synchAllChannelsCommand = new DelegateCommand(DoSyncSyncAllChannelsCommand, CanDoSyncAllChannelsCommand);

                return _synchAllChannelsCommand;
            }
        }

        /// <summary>
        /// Determines if the SyncAllChannelCommand can be executed.
        /// </summary>
        /// <returns>
        /// True if this <see cref="RSSFeedReader.ViewModels.MainViewModel"/> instance contains one or more instances
        /// of the <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/> class.
        /// </returns>
        bool CanDoSyncAllChannelsCommand()
        {
            return HasChannels() &&
                ChannelDataSource.Instance.IsNetworkAvailable;
        }

        /// <summary>
        /// Executes the SyncAllCommand.
        /// </summary>
        void DoSyncSyncAllChannelsCommand()
        {
            SetIsLoadingAll();
            ChannelDataSource.Instance.SyncAllChannels();
        }

        #endregion

        #region ImportFeedsCommand
        /// <summary>
        /// Imports feeds from a file.
        /// </summary>
        public ICommand ImportFeedsCommand
        {
            get
            {
                if (_importFeedsCommand == null)
                    _importFeedsCommand = new DelegateCommand(DoImportFeedsCommand);

                return _importFeedsCommand;
            }
        }

        /// <summary>
        /// Executes the importFeedsCommand.
        /// </summary>
        void DoImportFeedsCommand()
        {
            string fileName = string.Empty;
            Forms.DialogResult dialogResult;

            dialogResult = DisplayMessage.OpenFileDialog(Strings.MainView_ImportFeedsDialogTitle,
                 Strings.OpmlFiles + "|" + Strings.OpmlExtension, out fileName);

            if (dialogResult == Forms.DialogResult.OK)
            {
                try
                {
                    ImportExport.Import(fileName);

                    DisplayMessage.ShowMessage(Strings.MainView_ImportFeedsSuccessful,
                            Strings.ApplicationName, Forms.MessageBoxButtons.OK,
                            Forms.MessageBoxIcon.Information);
                }
                catch (ImportExportException ex)
                {
                    string msg = string.Format(Strings.MainView_ImportFeedsError, ex.Message);
                    if (ex.InnerException != null)
                        msg += "\tInnerException:" + ex.InnerException.Message;

                    DisplayMessage.ShowMessage(msg,
                        Strings.ApplicationName,
                        Forms.MessageBoxButtons.OK,
                        Forms.MessageBoxIcon.Error);

                    Logger.LogMessage(Strings.LogTypeFatalError, msg);
                }
            }
        }
        #endregion

        #region ExportFeedsCommand
        /// <summary>
        /// Exports the user feeds to a file.
        /// </summary>
        public ICommand ExportFeedsCommand
        {
            get
            {
                if (_exportCommand == null)
                    _exportCommand = new DelegateCommand(DoExportFeedsCommand, CanDoExportFeedsCommand);

                return _exportCommand;
            }
        }

        /// <summary>
        /// Determines if the ExportFeedsCommand can be executed.
        /// </summary>
        /// <returns>
        /// True if this <see cref="RSSFeedReader.ViewModels.MainViewModel"/> instance contains one or more instances
        /// of the <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/> class.
        /// </returns>
        bool CanDoExportFeedsCommand()
        {
            return HasChannels();
        }

        /// <summary>
        /// Executes the ExportFeedsCommand.
        /// </summary>
        void DoExportFeedsCommand()
        {
            string fileName = string.Empty;
            Forms.DialogResult dialogResult;

            dialogResult = DisplayMessage.SaveFileDialog(Strings.MainView_ExportFeedsDialogTitle,
                 Strings.OpmlFiles + "|" + Strings.OpmlExtension, out fileName);

            if (dialogResult == Forms.DialogResult.OK)
            {
                try
                {
                    ImportExport.Export(fileName);
                    DisplayMessage.ShowMessage(Strings.MainView_ExportFeedsSuccessful,
                            Strings.ApplicationName, Forms.MessageBoxButtons.OK,
                            Forms.MessageBoxIcon.Information);
                }
                catch (ImportExportException ex)
                {
                    string msg = string.Format(Strings.MainView_ExportFeedsError, ex.Message);
                    if (ex.InnerException != null)
                        msg += "\tInnerException:" + ex.InnerException.Message;

                    DisplayMessage.ShowMessage(msg,
                        Strings.ApplicationName,
                        Forms.MessageBoxButtons.OK,
                        Forms.MessageBoxIcon.Error);

                    Logger.LogMessage(Strings.LogTypeFatalError, msg);
                }
            }
        }
        #endregion

        #region DeleteChannelCommand
        /// <summary>
        /// Deletes the currently selected <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/> instance.
        /// </summary>
        public ICommand DeleteChannelCommand
        {
            get
            {
                if (_deleteChannelCommand == null)
                    _deleteChannelCommand = new DelegateCommand(DoDeleteChannelCommand, CanDoDeleteChannelCommand);

                return _deleteChannelCommand;
            }
        }

        /// <summary>
        /// Determines if the DeleteChannelCommand can be executed.
        /// </summary>
        /// <returns>
        /// True if this <see cref="RSSFeedReader.ViewModels.MainViewModel"/> instance contains one or more instances
        /// of the <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/> class.
        /// </returns>
        bool CanDoDeleteChannelCommand()
        {
            return HasChannels();
        }

        /// <summary>
        /// Executes the DeleteChannelCommand.
        /// </summary>
        void DoDeleteChannelCommand()
        {
            string message;

            if (CurrentChannel.ChannelType == ChannelType.Feed)
            {
                message = Strings.DeleteFeedConfirmation;
            }
            else
            {
                message = Strings.DeleteFolderConfirmation;
            }

            Forms.DialogResult dialogResult = DisplayMessage.ShowMessage(
                message,
                Strings.ApplicationName,
                Forms.MessageBoxButtons.YesNo,
                Forms.MessageBoxIcon.Warning);

            if (dialogResult == Forms.DialogResult.Yes)
            {
                ChannelDataSource.Instance.RemoveChannel(CurrentChannel.ChannelID);
                CurrentChannel = null;
            }
        }
        #endregion

        #region DeleteAllChannelsCommand
        /// <summary>
        /// Deletes all the Channels.
        /// </summary>
        public ICommand DeleteAllChannelsCommand
        {
            get
            {
                if (_deleteAllChannelsCommand == null)
                    _deleteAllChannelsCommand = new DelegateCommand(DoDeleteAllChannelsCommand, CanDoDeleteAllChannelsCommand);

                return _deleteAllChannelsCommand;
            }
        }

        /// <summary>
        /// Determines if the DeleteAllChannelsCommand can be executed.
        /// </summary>
        /// <returns>
        /// True if this <see cref="RSSFeedReader.ViewModels.MainViewModel"/> instance contains one or more instances
        /// of the <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/> class.
        /// </returns>
        bool CanDoDeleteAllChannelsCommand()
        {
            return HasChannels();
        }

        /// <summary>
        /// Executes the DeleteAllChannelsCommand.
        /// </summary>
        void DoDeleteAllChannelsCommand()
        {
            Forms.DialogResult dialogResult = DisplayMessage.ShowMessage(
                Strings.MainViewModel_DeleteAll,
                Strings.ApplicationName,
                Forms.MessageBoxButtons.YesNo,
                Forms.MessageBoxIcon.Warning);

            if (dialogResult == Forms.DialogResult.Yes)
            {
                Channels.Clear();
                CurrentChannel = null;
                ChannelDataSource.Instance.RemoveAllChannels();
                Application.Current.MainWindow.Title = Strings.ApplicationName;
            }
        }
        #endregion

        #region CopyLinkCommand
        /// <summary>
        /// Copied the shortcut to the feed homepage.
        /// </summary>
        public ICommand CopyLinkCommand
        {
            get
            {
                if (_copyLinkCommand == null)
                    _copyLinkCommand = new DelegateCommand(DoCopyLinkCommand);

                return _copyLinkCommand;
            }
        }

        /// <summary>
        /// Executes the CopyLinkCommand.
        /// </summary>
        void DoCopyLinkCommand()
        {
            Clipboard.SetText(CurrentChannel.SelectedPost.Link);
        }
        #endregion

        #region ManageFeedsCommand
        /// <summary>
        /// Opens the manage feeds window.
        /// </summary>
        public ICommand ManageFeedsCommand
        {
            get
            {
                if (_manageFeedsCommand == null)
                    _manageFeedsCommand = new DelegateCommand(DoManageFeedsCommand);

                return _manageFeedsCommand;
            }
        }

        /// <summary>
        /// Executes the OptionsCommand.
        /// </summary>
        void DoManageFeedsCommand()
        {
            DisplayWindow.ShowManageFeedsView();
        }
        #endregion

        #endregion

        #region Internal Properties
        /// <summary>
        /// Gets an ObservableCollection of type <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/>.
        /// </summary>
        internal static ObservableCollection<ChannelViewModel> ChannelCollection
        {
            get { return _channelCollection; }
        }
        #endregion

        #region Private Methods

        /// <summary>
        /// Finds a <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/> in the local collection based on the ID.
        /// </summary>
        /// <param name="id">
        /// The id to look for in the Collection of ChannelViewModel.
        /// </param>
        /// <returns>
        /// A <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/> instance if found, null otherwise.
        /// </returns>
        ChannelViewModel FindChannelViewModelById(Guid id)
        {
            ChannelViewModel foundChannelVM = null;

            foreach (ChannelViewModel channelViewModel in Channels)
            {
                foundChannelVM = FindChannelViewModelById(channelViewModel, id).FirstOrDefault();

                if (foundChannelVM != null)
                    return foundChannelVM;
            }

            Debug.WriteLine(string.Format("No ChannelViewModel with '{0}' id could be found, why?", id));
            return null;
        }

        /// <summary>
        /// Finds a <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/> instance,
        /// whose ChannelViewModel.ID is equal to the ID passed in.
        /// </summary>
        /// <param name="channelViewModel">
        /// The <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/> to test against
        /// the condition.
        /// </param>
        /// <param name="id">
        /// The ID to compare against.
        /// </param>
        /// <returns>
        /// A IEnumerable collection of type <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/>.
        /// </returns>
        IEnumerable<ChannelViewModel> FindChannelViewModelById(ChannelViewModel channelViewModel,
            Guid id)
        {
            if (channelViewModel.ChannelID == id)
                yield return channelViewModel;

            foreach (ChannelViewModel child in channelViewModel.Children)
                foreach (ChannelViewModel isSameId in FindChannelViewModelById(child, id))
                    yield return isSameId;
        }

        /// <summary>
        /// Initialize the <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/>.
        /// </summary>
        /// <param name="channelViewModel">The <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/>
        /// to initialize.</param>
        void InitializeChannelViewModel(ChannelViewModel channelViewModel)
        {
            channelViewModel.PropertyChanged += OnChannelViewModelPropertyChanged;
            foreach (ChannelViewModel channelVM in channelViewModel.Children)
            {
                InitializeChannelViewModel(channelVM);
            }
        }

        /// <summary>
        /// Uninitialize the <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/>.
        /// </summary>
        /// <param name="channelViewModel">The <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/>
        /// to uninitialize.</param>
        void UnInitializeChannelViewModel(ChannelViewModel channelViewModel)
        {
            channelViewModel.PropertyChanged -= OnChannelViewModelPropertyChanged;
            foreach (ChannelViewModel channelVM in channelViewModel.Children)
            {
                UnInitializeChannelViewModel(channelVM);
            }
        }

        /// <summary>
        /// Determines if this <see cref="RSSFeedReader.ViewModels.MainViewModel"/> instance
        /// contains at least one <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/> instance.
        /// </summary>
        /// <returns>
        /// True if one or more ChannelViewModels are found.
        /// </returns>
        bool HasChannels()
        {
            return Channels.Count > 0 && Channels != null;
        }

        /// <summary>
        /// Determines if the ChannelLoadedEventArgs raised an error.
        /// </summary>
        /// <param name="e">Event arguments describing the event.</param>
        /// <returns>True if an error occured.</returns>
        bool IsErrorOnDownload(ChannelAddedEventArgs e)
        {
            FeedLoadException error = e.Error as FeedLoadException;

            if (error != null)
            {
                DisplayMessage.ShowMessage(
                    string.Format(Strings.MainViewModel_FeedLoadException, error.Message,
                    error.InnerException.Message),
                    Strings.ApplicationName,
                    Forms.MessageBoxButtons.OK,
                    Forms.MessageBoxIcon.Information);

                Logger.LogMessage(Strings.LogTypeWarning,
                    string.Format(Strings.MainViewModel_FeedLoadException, error.Message,
                    error.InnerException.Message));

                return true;
            }
            return false;
        }

        /// <summary>
        /// Determines if the ChannelSynchronisedEventArgs raised an error.
        /// </summary>
        /// <param name="e">Event arguments describing the event.</param>
        /// <returns>True if an error occured.</returns>
        bool IsErrorOnSynchronise(ChannelSynchronisedEventArgs e)
        {
            FeedSynchroniseException error = e.Error as FeedSynchroniseException;
            ChannelViewModel channelViewModel;

            if (error != null)
            {
                try
                {
                    channelViewModel = FindChannelViewModelById(error.ChannelID);
                    channelViewModel.IsLoading = false;
                }
                catch { }

                Logger.LogMessage(Strings.LogTypeWarning,
                    string.Format(Strings.MainViewModel_FeedSynchroniseException, error.Message,
                    error.InnerException.Message));

                return true;
            }
            return false;
        }

        /// <summary>
        /// Set all the Channels to IsLoading = true.
        /// </summary>
        void SetIsLoadingAll()
        {
            foreach (ChannelViewModel channel in Channels)
            {
                SetIsLoading(channel);
            }
        }

        /// <summary>
        /// Set the specified ChannelViewModel.IsLoading property to true.
        /// </summary>
        /// <param name="channelViewModel">The ChannelViewModel to change the IsLoading property.</param>
        void SetIsLoading(ChannelViewModel channelViewModel)
        {
            if (channelViewModel.Children.Count > 0)
            {
                foreach (ChannelViewModel channelChild in channelViewModel.Children)
                    SetIsLoading(channelChild);
            }
            else
            {
                if (channelViewModel.ChannelType == ChannelType.Folder)
                    return;

                channelViewModel.IsLoading = true;
            }
        }

        #endregion

        #region Event Handlers

        /// <summary>
        /// Event handler for when the timer period elapses.
        /// </summary>
        /// <param name="sender">
        /// The source of the event.
        /// </param>
        /// <param name="e">
        /// Event arguments describing the event.
        /// </param>
        void OnRefreshTimer(object sender, EventArgs e)
        {
            if (Channels.Count == 0)
                return;

            SetIsLoadingAll();
            ChannelDataSource.Instance.SyncAllChannels();
            Debug.WriteLine("Refresh feeds called @ " + DateTime.Now.ToString());
        }

        /// <summary>
        /// Event handler for when a channel feed is added.
        /// </summary>
        /// <param name="sender">
        /// The source of the event.
        /// </param>
        /// <param name="e">
        /// Event arguments describing the event.
        /// </param>
        void OnChannelAdded(object sender, ChannelAddedEventArgs e)
        {
            if (IsErrorOnDownload(e))
                return;

            // Create the new ChannelViewModel and add to the _channelFolders collection.
            var channelViewModel = ChannelViewModel.CreateNew(e.NewChannel);
            InitializeChannelViewModel(channelViewModel);

            // Add to the _channelFolders collection.
            _channels.Add(channelViewModel);
            _channelCollection = _channels;
        }

        /// <summary>
        /// Event handler for when a channel feed is synched.
        /// </summary>
        /// <param name="sender">
        /// The source of the event.
        /// </param>
        /// <param name="e">
        /// Event arguments describing the event.
        /// </param>
        void OnChannelSynched(object sender, ChannelSynchronisedEventArgs e)
        {
            ChannelViewModel channelViewModelSynched = null;
            PostViewModel previousPost = null;
            string previousPostLink = string.Empty;

            if (IsErrorOnSynchronise(e))
                return;

            // Find the ChannelViewModel this will affect, clear the
            // ObservableCollection Posts property and add the
            // results from the sync.
            try
            {
                channelViewModelSynched = FindChannelViewModelById(e.SynchronisedChannel.ChannelID);
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
                Logger.LogMessage(Strings.LogTypeInformation, ex.Message);
                return;
            }

            if (channelViewModelSynched.SelectedPost != null)
            {
                // Keep a record of the previously selected post
                previousPostLink = channelViewModelSynched.SelectedPost.Link;
            }

            channelViewModelSynched.Posts.Clear();

            e.SynchronisedChannel.Posts.ForEach(delegate(Post post)
                {
                    channelViewModelSynched.Posts.Add(new PostViewModel(post));
                });

            _lastUpdated = DateTime.Now;
            channelViewModelSynched.IsLoading = false;

            // Find the previously selected post in the new list
            // and if found select it
            if (!string.IsNullOrEmpty(previousPostLink))
            {
                try
                {
                    previousPost = channelViewModelSynched.Posts.Single(post => post.Link == previousPostLink) as PostViewModel;
                    int postIndex = channelViewModelSynched.Posts.IndexOf(previousPost);
                    channelViewModelSynched.Posts[postIndex].IsSelected = true;

                    return;
                }
                catch (Exception ex)
                {
                    Debug.WriteLine(ex.Message);
                    Logger.LogMessage(Strings.LogTypeInformation, ex.Message);
                }
            }

            // Only get this far, if the previously selected post could not be found.
            // Set the selected post to null if not the current channel
            // so that we get a true count of new post as the top most post would be selected
            // otherwise thus decrementing the count of new posts by 1
            if (!channelViewModelSynched.Equals(CurrentChannel))
            {
                channelViewModelSynched.SelectedPost = null;
            }
            else
            {
                channelViewModelSynched.Posts[0].IsSelected = true;
            }
        }

        /// <summary>
        /// Event handler for when a channel feed or folder is deleted.
        /// </summary>
        /// <param name="sender">
        /// The source of the event.
        /// </param>
        /// <param name="e">
        /// Event arguments describing the event.
        /// </param>
        void OnChannelRemoved(object sender, ChannelRemovedEventArgs e)
        {
            ChannelViewModel channelViewModelToRemove = FindChannelViewModelById(e.OldChannel.ChannelID);

            if (channelViewModelToRemove == null)
                return;

            UnInitializeChannelViewModel(channelViewModelToRemove);

            if (channelViewModelToRemove.Parent != null)
            {
                ChannelViewModel parent =
                    FindChannelViewModelById(channelViewModelToRemove.Parent.ChannelID);
                parent.Children.Remove(channelViewModelToRemove);
            }
            else
            {
                Channels.Remove(channelViewModelToRemove);
            }
        }

        /// <summary>
        /// Event handler for when a channel folder is added.
        /// </summary>
        /// <param name="sender">
        /// The source of the event.
        /// </param>
        /// <param name="e">
        /// Event arguments describing the event.
        /// </param>
        void OnChannelFolderAdded(object sender, ChannelFolderAddedEventArgs e)
        {
            if (e.Parent == null)
            {
                var channelViewModel = ChannelViewModel.CreateNew(e.NewFolder);
                InitializeChannelViewModel(channelViewModel);

                _channels.Add(channelViewModel);
                _channelCollection = _channels;
            }
            else
            {
                var channelViewModelParent = FindChannelViewModelById(e.Parent.ChannelID);
                var channelViewModel = ChannelViewModel.CreateNew(e.NewFolder);
                InitializeChannelViewModel(channelViewModel);

                channelViewModelParent.Children.Add(channelViewModel);
                channelViewModel.Parent = channelViewModelParent;
                _channelCollection = _channels;
            }
        }

        /// <summary>
        /// Event handler for when a channel or folder is
        /// moved to another folder.
        /// </summary>
        /// <param name="sender">
        /// The source of the event.
        /// </param>
        /// <param name="e">
        /// Event arguments describing the event.
        /// </param>
        void OnChannelMovedToFolder(object sender, ChannelMovedToFolderEventArgs e)
        {
            ChannelViewModel folderViewModel = null;
            ChannelViewModel channelViewModelToMove = null;

            channelViewModelToMove = FindChannelViewModelById(e.ChannelToMove.ChannelID);

            if (e.DestinationFolder != null) // Could be null if moving to root
                folderViewModel = FindChannelViewModelById(e.DestinationFolder.ChannelID);

            if (channelViewModelToMove.Parent != null && folderViewModel != null)
            {
                // Moving from folder to folder
                ChannelViewModel parent = FindChannelViewModelById(channelViewModelToMove.Parent.ChannelID);
                parent.Children.Remove(channelViewModelToMove);
                folderViewModel.Children.Add(channelViewModelToMove);
                channelViewModelToMove.Parent = folderViewModel;
                folderViewModel.IsExpanded = true;
            }
            else if (folderViewModel != null)
            {
                // Coming from root to a folder
                Channels.Remove(channelViewModelToMove);
                folderViewModel.Children.Add(channelViewModelToMove);
                channelViewModelToMove.Parent = folderViewModel;
                folderViewModel.IsExpanded = true;
            }
            else
            {
                // Must be moving back to the root
                ChannelViewModel parent = FindChannelViewModelById(channelViewModelToMove.Parent.ChannelID);
                parent.Children.Remove(channelViewModelToMove);
                channelViewModelToMove.Parent = null;
                Channels.Add(channelViewModelToMove);
            }
        }

        /// <summary>
        /// Event handler for when a property of the <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/>
        /// instance changes.
        /// </summary>
        /// <param name="sender">
        /// The source of the event.
        /// </param>
        /// <param name="e">
        /// Event arguments describing the event.
        /// </param>
        void OnChannelViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            string IsSelected = "IsSelected";
            string Title = "Title";
            ChannelViewModel channelVM = sender as ChannelViewModel;

            if (e.PropertyName == IsSelected && channelVM.IsSelected)
            {
                CurrentChannel = channelVM;
                Application.Current.MainWindow.Title = string.Format(Strings.MainView_Title_CurrentChannel, CurrentChannel.Title);
            }
            else if (e.PropertyName == Title && CurrentChannel == channelVM)
            {
                Application.Current.MainWindow.Title = string.Format(Strings.MainView_Title_CurrentChannel, CurrentChannel.Title);
            }
        }

        /// <summary>
        /// Event handler for DispatcherUnhandledException.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">Event arguments describing the event.</param>
        void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
        {
            StringBuilder error = new StringBuilder();
            error.Append(string.Format("An unhandled application error has occured message: {0}", e.Exception.Message));

            if (e.Exception.InnerException != null)
                error.Append(string.Format("\tInnerException: {0}", e.Exception.InnerException.Message));

            error.Append(string.Format("\tStack trace: {0}", e.Exception.StackTrace));

            DisplayMessage.ShowMessage(Strings.DispatcherUnhandledExceptionMessage,
            Strings.ApplicationName, Forms.MessageBoxButtons.OK,
            Forms.MessageBoxIcon.Warning);

            Logger.LogMessage(Strings.LogTypeFatalError, error.ToString());
            e.Handled = true;
        }

        /// <summary>
        /// Event handler for when a channel is moved.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">Event arguments describing the event.</param>
        void OnChannelDragDropCompleted(object sender, MyTreeViewDragEventArgs e)
        {
            if (e.Destination is ChannelViewModel)
            {
                ChannelDataSource.Instance.MoveChannel(e.Source.ChannelID, ((ChannelViewModel)e.Destination).ChannelID);
            }
            else if (e.Destination is MainViewModel || e.Destination is ManageFeedsViewModel)
            {
                ChannelDataSource.Instance.MoveChannel(e.Source.ChannelID, Guid.Empty);
            }
            else
            {
                Debug.Fail("Object destination not expected type " + e.Destination.GetType().ToString());
            }
        }

        /// <summary>
        /// Event handler for when a channel is moved.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">Event arguments describing the event.</param>
        void OnChannelMoved(object sender, ChannelMovedEventArgs e)
        {
            ChannelViewModel channelToMove;
            ChannelViewModel channelToMoveParent;
            ChannelViewModel channelToMoveTo;
            ChannelViewModel channelToMoveToParent;
            int channelToMoveToIndex = -1;

            channelToMove = Common.FindChannelViewModelById(e.ChannelToMove.ChannelID, _channels.ToList());
            Debug.Assert(channelToMove != null);
            channelToMoveParent =
                channelToMove.Parent == null ? null : Common.FindChannelViewModelById(channelToMove.Parent.ChannelID, _channels.ToList());

            if (e.ChannelToMoveTo == null)
            {
                // Must be moving back to the root
                channelToMoveParent.Children.Remove(channelToMove);
                channelToMove.Parent = null;
                _channels.Add(channelToMove);
                return;
            }

            channelToMoveTo = Common.FindChannelViewModelById(e.ChannelToMoveTo.ChannelID, _channels.ToList());
            Debug.Assert(channelToMoveTo != null);
            channelToMoveToParent =
                channelToMoveTo.Parent == null ? null : Common.FindChannelViewModelById(channelToMoveTo.Parent.ChannelID, _channels.ToList());

            if (channelToMoveParent != null && channelToMoveToParent != null)
            {
                channelToMoveToIndex = channelToMoveToParent.Children.IndexOf(channelToMoveTo);
                if (channelToMoveParent == channelToMoveToParent)
                {
                    // Moving from within the same folder
                    if (channelToMoveTo == channelToMoveToParent.Children.Last())
                    {
                        channelToMoveParent.Children.Remove(channelToMove);
                        channelToMoveToParent.Children.Add(channelToMove);
                    }
                    else if (channelToMoveTo == channelToMoveToParent.Children.First())
                    {
                        channelToMoveParent.Children.Remove(channelToMove);
                        channelToMoveToParent.Children.Insert(0, channelToMove);
                    }
                    else
                    {
                        channelToMoveParent.Children.Remove(channelToMove);
                        channelToMoveToParent.Children.Insert(channelToMoveToIndex, channelToMove);
                    }
                    channelToMove.Parent = channelToMoveToParent;
                }
                else
                {
                    // Moving from another folder
                    channelToMoveParent.Children.Remove(channelToMove);

                    if (channelToMoveTo == channelToMoveToParent.Children.Last())
                    {
                        channelToMoveToParent.Children.Add(channelToMove);
                    }
                    else if (channelToMoveTo == channelToMoveToParent.Children.First())
                    {
                        channelToMoveToParent.Children.Insert(0, channelToMove);
                    }
                    else
                    {
                        channelToMoveToParent.Children.Insert(channelToMoveToIndex, channelToMove);
                    }
                    channelToMove.Parent = channelToMoveToParent;
                }
            }
            else if (channelToMoveParent == null && channelToMoveToParent != null)
            {
                // Coming from root to a folder
                channelToMoveToIndex = channelToMoveToParent.Children.IndexOf(channelToMoveTo);
                _channels.Remove(channelToMove);

                if (channelToMoveTo == channelToMoveToParent.Children.Last())
                {
                    channelToMoveToParent.Children.Add(channelToMove);
                }
                else if (channelToMoveTo == channelToMoveToParent.Children.First())
                {
                    channelToMoveToParent.Children.Insert(0, channelToMove);
                }
                else
                {
                    channelToMoveToParent.Children.Insert(channelToMoveToIndex + 1, channelToMove);
                }

                channelToMove.Parent = channelToMoveToParent;
            }
            else if (channelToMoveParent != null && channelToMoveToParent == null)
            {
                // Coming from folder to root at a specified feed
                channelToMoveToIndex = _channels.IndexOf(channelToMoveTo);
                channelToMoveParent.Children.Remove(channelToMove);

                if (channelToMoveTo == _channels.Last())
                {
                    _channels.Add(channelToMove);
                }
                else if (channelToMoveTo == _channels.First())
                {
                    _channels.Insert(0, channelToMove);
                }
                else
                {
                    _channels.Insert(channelToMoveToIndex + 1, channelToMove);
                }

                channelToMove.Parent = null;
            }
            else if (channelToMoveParent == null && channelToMoveToParent == null)
            {
                channelToMoveToIndex = _channels.IndexOf(channelToMoveTo);

                // Moving from within the same folder
                if (channelToMoveTo == _channels.Last())
                {
                    _channels.Remove(channelToMove);
                    _channels.Add(channelToMove);
                }
                else if (channelToMoveTo == _channels.First())
                {
                    _channels.Remove(channelToMove);
                    _channels.Insert(0, channelToMove);
                }
                else
                {
                    _channels.Remove(channelToMove);
                    _channels.Insert(channelToMoveToIndex, channelToMove);
                }
                channelToMove.Parent = channelToMoveToParent;
            }
            else
            {
                Debug.WriteLine("Unable to move channel");
                Logger.LogMessage(Strings.LogTypeFatalError, "Unable to move channel");
            }
        }

        #endregion
    }
}
